package com.lagou.factory;

import com.lagou.config.BeanDefinition;
import com.lagou.config.BeanDefinitionProcess;
import com.lagou.config.BeanDefinitionRegistry;
import com.lagou.config.BeanProcess;
import com.lagou.config.DependInfo;
import com.lagou.config.Registry;
import com.lagou.config.SimpleAliasRegistry;
import com.lagou.util.ClassUtil;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * @author: wudong
 * @create: 2021-05-26 14:03
 **/
public abstract class ApplicatonBeanFacotry extends SimpleAliasRegistry implements BeanFactory {
    protected final Logger logger = Logger.getLogger(this.getClass());

    private final Map<String,Object> beanContext = new HashMap<>();

    private final Registry<BeanDefinition> beanDefinitions = new BeanDefinitionRegistry();

    private final Registry<BeanDefinition> beanProcess = new BeanDefinitionRegistry();

    private final Registry<BeanDefinition> defineProcess = new BeanDefinitionRegistry();

    //防止重复初始化
    private Map<BeanDefinition,Object> loadHistry = new HashMap<>();



    @Override
    public Object getBean(String name){
        Object obj = beanContext.get(name);
        if(obj == null){
            String realName = this.getRealName(name);
            if(realName != null){
                obj = beanContext.get(realName);
            }
        }
        return obj;
    }

    @Override
    public <T> T getBean(Class<T> requiredType){
        String beanName = requiredType.getName();
        return (T)getBean(beanName);
    }

    /**
     * 定义容器加载主流程,模板方法
     */
    public final void refresh(){
        try{
            //准备工作
            prepare();

            //初始化所有bean定义
            Consumer<BeanDefinition> registry = new Consumer<BeanDefinition>() {
                @Override
                public void accept(BeanDefinition register) {
                    if(BeanProcess.class.isAssignableFrom(register.getBeanClass())){
                        beanProcess.registerBeanDefinition(null,register);
                    }else if(BeanDefinitionProcess.class.isAssignableFrom(register.getBeanClass())){
                        defineProcess.registerBeanDefinition(null,register);
                    }else {
                        beanDefinitions.registerBeanDefinition(null,register);
                    }
                }
            };
            initBeanDefiniton(registry);

            //准备BeanDefinitionProcess类型
            prepareBeanDefinitionProcess(registry);

            //init bean
            //TODO factoryBean依赖的属性是非代理对象, 暂时未想到解决方案
            initBean();

            //处理BeanProcess类型
            postProcessBean();

            initBeanProper();

        }catch (Exception e){
            throw new IllegalStateException("初始化容器失败",e);
        }


    }

    protected abstract void prepare();

    protected abstract void initBeanDefiniton(Consumer<BeanDefinition> registry);


    /**
     * BeanDefinitionProcess对象必须优先初始化完成
     * @param registry
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void prepareBeanDefinitionProcess(Consumer<BeanDefinition> registry) throws IllegalAccessException, InstantiationException {
        List<Registry.RegisterInfo<BeanDefinition>> registerInfos = defineProcess.listAllRegister();

        List<BeanDefinitionProcess> beanDefinitionProcesses = new ArrayList<>(registerInfos.size());
        for (Registry.RegisterInfo<BeanDefinition> registerInfo : registerInfos) {
            BeanDefinition beanDefinition = registerInfo.getRegister();

           // /* BeanDefinition类型的bean不支持注入bean. 因为此时容器尚未开始实例化bean.
           //  如果支持注入的话存在的问题:
           //       1 BeanDefinition依赖的bean要是完全初始化完成的才行. 因为随后会调用BeanDefinition.postProcessAfterBeanDefiInit
           //          方法,如果依赖的bean未初始化完成就会出现问题
           //       2 如果注入初始化完成的bean就没法解决循环依赖问题
           //  从设计上看的话, 因为这个bean是容器初始化bean之前的准备工作.所以不该依赖随后的容器功能
           //  */
            Object obj = createAndRegisterBean(beanDefinition);
            beanDefinitionProcesses.add((BeanDefinitionProcess)obj);
        }

        for (BeanDefinitionProcess beanDefinitionProcess : beanDefinitionProcesses) {
            beanDefinitionProcess.postProcessAfterBeanDefiInit(registry);
        }
    }

    private void initBean() throws InstantiationException, IllegalAccessException {
        List<Registry.RegisterInfo<BeanDefinition>> beanInfos = beanDefinitions.listAllRegister();
        List<Registry.RegisterInfo<BeanDefinition>> processInfos = beanProcess.listAllRegister();

        for (Registry.RegisterInfo<BeanDefinition> beanInfo : beanInfos) {
            createAndRegisterBean(beanInfo.getRegister());
        }

        for (Registry.RegisterInfo<BeanDefinition> beanInfo : processInfos) {
            createAndRegisterBean(beanInfo.getRegister());
        }
    }

    private void postProcessBean(){
        List<Object> objects = new ArrayList<>(beanContext.values());

        List<BeanProcess> beanProcesses = new ArrayList<>();

        Iterator<Object> iterator = objects.iterator();
        while (iterator.hasNext()) {
            Object object = iterator.next();
            if(object instanceof BeanProcess){
                beanProcesses.add((BeanProcess)object);
                iterator.remove();
                continue;
            }

            if(object instanceof BeanDefinitionProcess){
                iterator.remove();
                continue;
            }
        }

        for (Object source : objects) {
            Class<?> sourceClass = source.getClass();
            Object update = source;
            for (BeanProcess process : beanProcesses) {
                update = process.postProcessAfterInitialization(update,sourceClass);
            }
            updateBean(source,update);
        }
    }

    private void updateBean(Object source,Object targe){
        String name = source.getClass().getName();
        Object replace = beanContext.replace(name, targe);
        if(replace == null){
            throw new IllegalStateException("容器中不存在" + name);
        }
    }


    /**
     * 根据bean定义创建bean
     * @param beanDefinition
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private Object createAndRegisterBean(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException {
        Object object = loadHistry.get(beanDefinition);
        if(object != null){
            return object;
        }
        if(beanDefinition.getFactoryBean() != null){
            object = beanDefinition.getFactoryBean().getObject();
        }else {
            object = beanDefinition.getBeanClass().newInstance();
        }

        registerBean(object,beanDefinition.getBeanNames());
        loadHistry.put(beanDefinition,object);
        return object;
    }

    private void initBeanProper(){
        //注入属性
        Set<Map.Entry<BeanDefinition,Object>> entries = loadHistry.entrySet();
        // 防止ConcurrentModificationException错误
        List<Map.Entry<BeanDefinition,Object>> beansDefin = new ArrayList<>(entries);
        for (Map.Entry<BeanDefinition,Object> entry : beansDefin) {
            loadField(entry.getValue(),entry.getKey().getDependsOn());
        }
    }

    private void loadField(Object target,List<DependInfo> dependsOn){
        for (DependInfo dependInfo : dependsOn) {
            Object depent = getBeanOrCreate(dependInfo);
            if(depent != null){
                dependInfo.setDependValue(target,depent);
            }
        }
    }

    private void registerBean(Object target,List<String> alias){
        //TODO 同一个接口的多个实现,注入到容器时.会出现通过接口查询bean的时候只能查询出来一个. 后期可以加入bean的排序
        String beanName = target.getClass().getName();
        if(this.beanContext.containsKey(beanName)){
            throw new IllegalStateException(beanName + "重复注册");
        }
        this.beanContext.put(beanName,target);

        if(alias != null){
            for (String alia : alias) {
                if(alia.isEmpty()){
                    continue;
                }
                registerAlias(beanName,alia);
            }
        }
        Consumer<Class<?>> classConsumer = aclass -> {
            registerAlias(beanName,aclass.getName());
        };
        ClassUtil.findAllClass(target.getClass(),classConsumer);
    }


    protected Object getBeanOrCreate(DependInfo dependInfo){
        Object object = null;
        String message = "";
        if (dependInfo.getName() != null && !dependInfo.getName().isEmpty()) {
            object = getBean(dependInfo.getName());
            if (object == null) {
                object = doCreateBean(dependInfo.getName());
            }
            message = "依赖bean:" + dependInfo.getName() + "不存在";
        } else if (dependInfo.getType() != null) {
            object = getBean(dependInfo.getType());
            if (object == null) {
                object = doCreateBean(dependInfo.getType());
            }
            message = "依赖bean类型:" + dependInfo.getType() + "不存在";
        }
        if(object == null && dependInfo.required()){
            throw new RuntimeException(message);
        }
        return object;
    }

    private Object doCreateBean(String name){
        BeanDefinition beanDefinition = beanDefinitions.getBeanDefinition(name);
        if(beanDefinition == null){
            // throw new IllegalArgumentException(name + "依赖不存在");
            return null;
        }
        try {
            return createAndRegisterBean(beanDefinition);
        } catch (Exception e) {
            throw new IllegalStateException(name + ",创建失败",e);
        }
    }

    private Object doCreateBean(Class<?> requiredType){
        return doCreateBean(requiredType.getName());
    }
}
